#include <fstream>
#include <string>
#include <glload/gl_3_3.h>
#include <glload/gll.hpp>
#include <GL/freeglut.h>
#include "Renderer.h"

Renderer *renderer;

//
// DebugFunction callback from glut in case of error.
//
void APIENTRY DebugFunc(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, GLvoid* userParam)
{
	std::string sourceName;
	switch(source)
	{
	case GL_DEBUG_SOURCE_API_ARB: sourceName = "API"; break;
	case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: sourceName = "Window System"; break;
	case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: sourceName = "Shader Compiler"; break;
	case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: sourceName = "Third Party"; break;
	case GL_DEBUG_SOURCE_APPLICATION_ARB: sourceName = "Application"; break;
	case GL_DEBUG_SOURCE_OTHER_ARB: sourceName = "Other"; break;
	}

	std::string errorName;
	switch(type)
	{
	case GL_DEBUG_TYPE_ERROR_ARB: errorName = "Error"; break;
	case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: errorName = "Deprecated Behaviour"; break;
	case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: errorName = "Undefined Behaviour"; break;
	case GL_DEBUG_TYPE_PORTABILITY_ARB: errorName = "Portability"; break;
	case GL_DEBUG_TYPE_PERFORMANCE_ARB: errorName = "Performance"; break;
	case GL_DEBUG_TYPE_OTHER_ARB: errorName = "Other"; break;
	}

	std::string severityName;
	switch(severity)
	{
	case GL_DEBUG_SEVERITY_HIGH_ARB: severityName = "High"; break;
	case GL_DEBUG_SEVERITY_MEDIUM_ARB: severityName = "Medium"; break;
	case GL_DEBUG_SEVERITY_LOW_ARB: severityName = "Low"; break;
	}

	printf("source: %s, error: %s, severity %s, message: %s\n",
		sourceName.c_str(),
		errorName.c_str(),
		severityName.c_str(),
		message
		);

}

void KeyboardFunc(unsigned char key, int x, int y)
{
	switch(key)
	{
	case 27:
		glutLeaveMainLoop();
		break;
	}
}

void DisplayFunc()
{
	static int lastTime = 0;
	int elapsedTime = glutGet(GLUT_ELAPSED_TIME) - lastTime;
	lastTime = glutGet(GLUT_ELAPSED_TIME);

	renderer->Update(elapsedTime * 0.001f);
	renderer->Render();

	glutPostRedisplay();
}

//
// Reshape function callback from glut in case of reshaping the window.
//
void ReshapeFunc(int width, int height)
{
	renderer->SetWindowSize(width, height);
}


//
// Entry point
//
int main(int argc, char** argv)
{
	// Setup freeglut
	glutInit(&argc, argv);

	int width = 800;
	int height = 600;
	unsigned int displayMode = GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH | GLUT_STENCIL;

	glutInitDisplayMode(displayMode);
	glutInitContextVersion(3, 3);
	glutInitContextProfile(GLUT_CORE_PROFILE);
#ifdef DEBUG
	glutInitContextFlags(GLUT_DEBUG);
#endif
	glutInitWindowSize(width, height);
	int window = glutCreateWindow("Orb Rendering Window");

	glload::LoadFunctions();

	glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);

	//
	if(!glload::IsVersionGEQ(3, 3))
	{
		printf("Your OpenGL version is %i, %i. You must have at least OpenGL 3.3 to run this application.\n",
			glload::GetMajorVersion(), glload::GetMinorVersion());
		glutDestroyWindow(window);
		return 0;
	}

	//
	if(glext_ARB_debug_output)
	{
		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
		glDebugMessageCallbackARB(DebugFunc, (void*)15);
	}

	renderer = new Renderer;
	renderer->Initialize();

	glutDisplayFunc(DisplayFunc);
	glutKeyboardFunc(KeyboardFunc);
	glutReshapeFunc(ReshapeFunc);
	glutMainLoop();

	renderer->CleanUp();
	delete renderer;

	return 0;
}